#include <glad/gl.h>
#include <GLFW/glfw3.h>

#include <iostream>

void key_callback(GLFWwindow* window, int key, int scancode, int action,
                  int mode);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void create_shaders(void);
void destroy_shaders();
void cleanup();
void create_vbo();
void create_vbo1();
void destroy_vbo();

const GLuint WIDTH = 800 * 2, HEIGHT = 600 * 2;

GLuint vertex_shader_id_, fragment_shader_id_, program_id_, vao_id_, vbo_id_;

GLuint vao_id_1_;
GLuint vbo_id_1_;

typedef struct {
    float XYZW[4];
    float RGBA[4];
} Vertex;

const GLchar* vertex_shader = R"(
    #version 330 core

    layout(location = 0) in vec4 in_Position;
    layout(location = 1) in vec4 in_Color;
    out vec4 ex_Color;

    void main()
    {
        gl_Position = in_Position;
        ex_Color = in_Color;
    }
)";

const GLchar* fragment_shader = R"(
    #version 330 core

    in vec4 ex_Color;
    out vec4 out_Color;

    void main()
    {
        out_Color = ex_Color;
    }
)";

int main() {
    glfwInit();

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);  // 可以调整窗口大小

    GLFWwindow* window =
        glfwCreateWindow(WIDTH, HEIGHT, "glad", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    if (window == nullptr) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwSetKeyCallback(window, key_callback);

    // Load OpenGL functions, gladLoadGL returns the loaded version, 0 on error.
    int version = gladLoadGL(glfwGetProcAddress);
    if (version == 0) {
        std::cout << "Failed to initialize OpenGL context" << std::endl;
        return -1;
    }

    std::cout << "Loaded OpenGL " << GLAD_VERSION_MAJOR(version) << "."
              << GLAD_VERSION_MINOR(version) << std::endl;

    // Define the viewport dimensions
    glad_glViewport(0, 0, WIDTH, HEIGHT);

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    create_shaders();
    create_vbo();
    create_vbo1();

    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glEnable(GL_DEPTH_TEST);

    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glBindVertexArray(vao_id_);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glBindVertexArray(vao_id_1_);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // Swap the screen buffers.
        glfwSwapBuffers(window);
    }

    cleanup();

    // Terminates GLFW, clearing any resources allocated by GLFW.
    glfwTerminate();

    return 0;
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action,
                  int mode) {
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, GL_TRUE);
    }
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void create_shaders(void) {
    GLenum error_check_value;

    vertex_shader_id_ = glad_glCreateShader(GL_VERTEX_SHADER);
    glad_glShaderSource(vertex_shader_id_, 1, &vertex_shader, nullptr);
    glad_glCompileShader(vertex_shader_id_);

    fragment_shader_id_ = glad_glCreateShader(GL_FRAGMENT_SHADER);
    glad_glShaderSource(fragment_shader_id_, 1, &fragment_shader, nullptr);
    glad_glCompileShader(fragment_shader_id_);

    program_id_ = glad_glCreateProgram();
    glad_glAttachShader(program_id_, vertex_shader_id_);
    glad_glAttachShader(program_id_, fragment_shader_id_);
    glad_glLinkProgram(program_id_);
    glad_glUseProgram(program_id_);

    // the shader object is no longer needed.
    glad_glDeleteShader(fragment_shader_id_);
    glad_glDeleteShader(vertex_shader_id_);

    error_check_value = glad_glGetError();
    if (error_check_value != GL_NO_ERROR) {
        std::fprintf(stderr, "ERROR: Cound not create the shaders: %d\n",
                     error_check_value);
        exit(-1);
    }
}

void destroy_shaders() {
    GLenum error_check_value;

    glad_glUseProgram(0);

    glad_glDetachShader(program_id_, vertex_shader_id_);
    glad_glDetachShader(program_id_, fragment_shader_id_);

    glad_glDeleteProgram(program_id_);

    error_check_value = glad_glGetError();
    if (error_check_value != GL_NO_ERROR) {
        fprintf(stderr, "ERROR: Could not destroy the shaders: %d\n",
                error_check_value);
        exit(-1);
    }
}

void cleanup() {
    destroy_shaders();
    destroy_vbo();
}

void create_vbo1(void) {
    Vertex vertices[] = {
        {{-0.9f, 0.9f, 0.8f, 1.0f}, {1.0f, 1.0f, 0.1f, 1.0f}},
        {{0.0f, 0.0f, -0.5f, 1.0f}, {0.1f, 0.3f, 0.9f, 1.0f}},
        {{-0.9f, -0.3f, 1.0f, 1.0f}, {1.0f, 0.0f, 1.0f, 1.0f}}};
    GLenum error_check_value;
    const size_t buffer_size = sizeof(vertices);
    const size_t vertex_size = sizeof(vertices[0]);
    const size_t rgb_offset = sizeof(vertices[0].XYZW);

    glad_glGenVertexArrays(1, &vao_id_1_);
    glad_glBindVertexArray(vao_id_1_);

    glad_glGenBuffers(1, &vbo_id_1_);
    glad_glBindBuffer(GL_ARRAY_BUFFER, vbo_id_1_);
    glad_glBufferData(GL_ARRAY_BUFFER, buffer_size, vertices, GL_STATIC_DRAW);

    glad_glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, vertex_size,
                               (GLvoid*)0);
    glad_glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, vertex_size,
                               (GLvoid*)rgb_offset);

    glad_glEnableVertexAttribArray(0);  // 與 vertex_shader 中的 location 對應
    glad_glEnableVertexAttribArray(1);

    error_check_value = glad_glGetError();
    if (error_check_value != GL_NO_ERROR) {
        fprintf(stderr, "ERROR: Could not create a VBO: %d\n",
                error_check_value);
        exit(-1);
    }
}

void create_vbo(void) {
    Vertex vertices[] = {{{-0.8f, -0.8f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, 1.0f}},
                         {{0.0f, 0.8f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f, 1.0f}},
                         {{0.8f, -0.8f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f, 1.0f}}};
    GLenum error_check_value;
    const size_t buffer_size = sizeof(vertices);
    const size_t vertex_size = sizeof(vertices[0]);
    const size_t rgb_offset = sizeof(vertices[0].XYZW);

    glad_glGenVertexArrays(1, &vao_id_);
    std::cout << "vao_id_ : " << vao_id_ << std::endl;
    glad_glBindVertexArray(vao_id_);

    glad_glGenBuffers(1, &vbo_id_);
    std::cout << "vbo_id_ : " << vbo_id_ << std::endl;
    glad_glBindBuffer(GL_ARRAY_BUFFER, vbo_id_);
    glad_glBufferData(GL_ARRAY_BUFFER, buffer_size, vertices, GL_STATIC_DRAW);

    glad_glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, vertex_size, 0);
    glad_glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, vertex_size,
                               (GLvoid*)rgb_offset);

    glad_glEnableVertexAttribArray(0);
    glad_glEnableVertexAttribArray(1);

    error_check_value = glad_glGetError();
    if (error_check_value != GL_NO_ERROR) {
        fprintf(stderr, "ERROR: Could not create a VBO: %d\n",
                error_check_value);
        exit(-1);
    }
}

void destroy_vbo() {
    GLenum error_check_value;

    glad_glDisableVertexAttribArray(1);
    glad_glDisableVertexAttribArray(0);

    glad_glBindBuffer(GL_ARRAY_BUFFER, 0);
    glad_glDeleteBuffers(1, &vbo_id_);

    glad_glBindVertexArray(vao_id_);
    glad_glDeleteVertexArrays(1, &vao_id_);

    error_check_value = glad_glGetError();
    if (error_check_value != GL_NO_ERROR) {
        fprintf(stderr, "ERROR: Could not destroy the VBO: %d\n",
                error_check_value);
        exit(-1);
    }
}
